package com.adv.ems.manager.impl;

import com.adv.ems.db.entity.BaseDO;
import com.adv.ems.utils.RedisUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.transaction.annotation.Transactional;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class BaseManagerImpl<M extends BaseMapper<T>, T extends BaseDO> extends ServiceImpl<M, T> implements SmartInitializingSingleton, BeanFactoryAware {
    private RedisUtil<T> redisUtil;
    private Class<T> entityClass;

    @Override
    public List<T> list() {
        return new ArrayList<>(getAllMap().values());
    }

    public Map<Long, T> getAllMap() {
        return redisUtil.getMap(entityClass);
    }
    @Transactional(
            rollbackFor = {Exception.class}
    )
    public void saveOrUpdateData(T entity) {
        try {
            T t = entityClass.newInstance();
            BeanUtils.copyProperties(entity, t);
            if (t.getId() == null) {
                this.save(t);
            }else {
                this.updateById(t);
            }
            entity.setId(t.getId());
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    @Transactional(
            rollbackFor = {Exception.class}
    )
    @Override
    public boolean save(T entity) {
        boolean r = super.save(entity);
        redisUtil.set(entity);
        return r;
    }

    @Override
    public T getById(Serializable id) {
        return redisUtil.get(entityClass, (Long) id);
    }

    @Transactional(
            rollbackFor = {Exception.class}
    )
    @Override
    public boolean removeById(T entity) {
        boolean b = super.removeById(entity);
        redisUtil.del(entityClass, entity.getId());
        return b;
    }

    @Transactional(
            rollbackFor = {Exception.class}
    )
    @Override
    public boolean removeBatchByIds(Collection<?> ids) {
        boolean b = super.removeBatchByIds(ids);
        for (Object id : ids) {
            redisUtil.del(entityClass, (Long) id);
        }
        return b;
    }

    @Transactional(
            rollbackFor = {Exception.class}
    )
    @Override
    public boolean removeBatchByIds(Collection<?> ids, int batchSize) {
        boolean b =  super.removeBatchByIds(ids, batchSize);
        for (Object id : ids) {
            redisUtil.del(entityClass, (Long) id);
        }
        return b;
    }

    @Transactional(
            rollbackFor = {Exception.class}
    )
    @Override
    public boolean removeById(Serializable id) {
        boolean b = super.removeById(id);
        redisUtil.del(entityClass, (Long) id);
        return b;
    }

    @Transactional(
            rollbackFor = {Exception.class}
    )
    @Override
    public boolean removeById(Serializable id, boolean useFill) {
        boolean b =  super.removeById(id, useFill);
        redisUtil.del(entityClass, (Long) id);
        return b;
    }

    @Transactional(
            rollbackFor = {Exception.class}
    )
    @Override
    public boolean updateBatchById(Collection<T> entityList) {
        boolean b =  super.updateBatchById(entityList);
        baseMapper.selectBatchIds(entityList.stream().map(BaseDO::getId).collect(Collectors.toList())).forEach(t -> redisUtil.set(t));
        return b;
    }

    @Transactional(
            rollbackFor = {Exception.class}
    )
    @Override
    public boolean updateBatchById(Collection<T> entityList, int batchSize) {
        boolean b =  super.updateBatchById(entityList, batchSize);
        baseMapper.selectBatchIds(entityList.stream().map(BaseDO::getId).collect(Collectors.toList())).forEach(t -> redisUtil.set(t));
        return b;
    }

    @Transactional(
            rollbackFor = {Exception.class}
    )
    @Override
    public boolean updateById(T entity) {
        boolean b =  super.updateById(entity);
        T t = baseMapper.selectById(entity.getId());
        redisUtil.set(t);
        return b;
    }

    @Transactional(
            rollbackFor = {Exception.class}
    )
    @Override
    public boolean removeBatchByIds(Collection<?> list, boolean useFill) {
        boolean b =  super.removeBatchByIds(list, useFill);
        List<Long> ids = list.stream().map(t -> ((BaseDO) t).getId()).collect(Collectors.toList());
        ids.forEach(id -> redisUtil.del(entityClass, id));
        return b;
    }

    @Transactional(
            rollbackFor = {Exception.class}
    )
    @Override
    public boolean removeBatchByIds(Collection<?> ids, int batchSize, boolean useFill) {
        boolean b =  super.removeBatchByIds(ids, batchSize, useFill);
        ids.forEach(id -> redisUtil.del(entityClass, (Long) id));
        return b;
    }

    @Transactional(
            rollbackFor = {Exception.class}
    )
    @Override
    public boolean removeByIds(Collection<?> list) {
        boolean b =  super.removeByIds(list);
        List<Long> ids = list.stream().map(t -> ((BaseDO) t).getId()).collect(Collectors.toList());
        ids.forEach(id -> redisUtil.del(entityClass, id));
        return b;
    }

    @Transactional(
            rollbackFor = {Exception.class}
    )
    @Override
    public boolean removeByIds(Collection<?> list, boolean useFill) {
        boolean b =  super.removeByIds(list, useFill);
        List<Long> ids = list.stream().map(t -> ((BaseDO) t).getId()).collect(Collectors.toList());
        ids.forEach(id -> redisUtil.del(entityClass, id));
        return b;
    }

    @Override
    public List<T> listByIds(Collection<? extends Serializable> idList) {
        return idList.stream().map(id -> redisUtil.get(entityClass, (Long) id)).filter(Objects::nonNull).collect(Collectors.toList());
    }

    public void refreshCache() {
        log.info("refresh cache 【{}】", entityClass.getSimpleName());
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        wrapper.last(" limit 50000 ");
        redisUtil.flush(entityClass);
        baseMapper.selectList(wrapper).forEach(t -> redisUtil.set(t));
    }

    @Override
    public void afterSingletonsInstantiated() {
        Class<? extends BaseManagerImpl> aClass = this.getClass();
        ParameterizedType genericSuperclass = (ParameterizedType)aClass.getGenericSuperclass();
        Type[] actualTypeArguments = genericSuperclass.getActualTypeArguments();
        for (Type type : actualTypeArguments) {
            try {
                Class<?> cls = Class.forName(type.getTypeName());
                Type superclass = cls.getGenericSuperclass();
                if (superclass == null) {
                    continue;
                }
                String typeName = cls.getGenericSuperclass().getTypeName();
                if (typeName.equals(BaseDO.class.getName())) {
                    entityClass = (Class<T>) cls;
                }
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
        if (entityClass == null) {
            throw new RuntimeException(String.format(aClass.getName() + " entityClass is null"));
        }
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.redisUtil = beanFactory.getBean(RedisUtil.class);
    }
}
